unit game_state;

{$mode objfpc}{$H+}

interface

uses SysUtils, sdl2, fgl, input_handler, texture_manager, game_object,glog;

type
  TGameObjectList = specialize TFPGList<TGameObject>;

  TGameState = class
  public
    stateId: string;
    constructor Create();

    //destructor Destroy(); override;

    procedure Update; virtual;

    procedure Render(); virtual;

    function OnExit: boolean; virtual;

    function OnEnter: boolean; virtual;

  protected
    pr: PSDL_Renderer;
    objects: TGameObjectList;
    readyState:TGameState;
  end;
  TGameStateList = specialize TFPGList<TGameState>;

  TGameStateMachine = class
  public
    destructor Destroy(); override;
    procedure PushState(state: TGameState);
    procedure ChangeState(state: TGameState);
    procedure PopState();
    procedure PopAndChangeState(state: TGameState);
    procedure Update;
    procedure Render();
    class function Instance(): TGameStateMachine;
  private
    states: TGameStateList;
    constructor Create();
  end;
implementation

function TGameState.OnExit: boolean;
var
  i: integer;
begin
  Writelnlog('game state [%s] will exit..',[stateId]);
  if Assigned(objects) then
  begin
    for i := 0 to objects.Count - 1 do
      objects[i].Free;
    objects.Clear;
    FreeAndNil(objects);
  end;
  Result := True;
end;

constructor TGameState.Create();
begin
  objects := TGameObjectList.Create;
  readyState := nil;
end;

(*
destructor TGameState.Destroy();
var
  i: Integer;
begin
  if objects = nil then exit;
  for i:=0 to objects.Count - 1 do
    objects[i].Free;

  objects.Clear;
  objects.Free;
end;
*)

procedure TGameState.Update;
var
  i: integer;
begin
  writelnlog(stateId);
  if Assigned(readyState) then
  begin
    writelnlog(readyState.stateId);
    (TGameStateMachine.Instance()).changeState(readyState);
    exit;
  end;
  for i := 0 to objects.Count - 1 do
    objects[i].Update;
end;

procedure TGameState.Render();
var
  i: integer;
begin
  for i := 0 to objects.Count - 1 do
    objects[i].Draw(pr);
end;

function TGameState.OnEnter: boolean;
begin

end;


var
  m: TGameStateMachine;

class function TGameStateMachine.Instance(): TGameStateMachine;
begin
  if m = nil then
    m := TGameStateMachine.Create;

  Result := m;
end;

procedure TGameStateMachine.PopAndChangeState(state: TGameState);
begin
  PopState();
  PopState();
  PushState(state);
end;

procedure TGameStateMachine.Update;
begin
  writelnlog('state :%s will update',[states.last.stateId]);
  if states.Count > 0 then
    (states.Last).Update();
end;

procedure TGameStateMachine.Render();
begin
  if states.Count > 0 then
    states.Last.Render();
end;

constructor TGameStateMachine.Create();
begin
  states := TGameStateList.Create;
  inherited Create;
end;

destructor TGameStateMachine.Destroy();
var i:integer;
begin
  for i :=0 to states.Count-1 do
    PopState();

  states.Free;
end;

procedure TGameStateMachine.PushState(state: TGameState);
begin
  writelnlog('states count :%d,push one',[states.Count]);
  states.Add(state);
  states.Last.OnEnter();
end;

procedure TGameStateMachine.ChangeState(state: TGameState);
var
  s: TGameState;
begin
  writelnlog('change status to [%s]',[state.stateId]);
  if states.Count > 0 then
  begin
    if states.Last.stateId = state.stateId then
      exit;


    s := states.Last;
    states.Remove(s);
    s.OnExit;
    FreeAndNil(s);
  end;

  PushState(state);
end;

procedure TGameStateMachine.PopState();
var
  state: TGameState;
begin
  writelnlog('states count :%d, pop one...',[states.count]);
  if states.Count = 0 then
    exit;

  state := states.Last;
  states.Remove(state);
  state.OnExit;
  writelnlog('exit ok');
  FreeAndNil(state);
  writelnlog('free ok : %d',[states.Count]);

end;
end.
